package service

import (
	"errors"
	"fmt"
	"strconv"
	"test/mysql/base"
	redis1 "test/redis"
	"test/redis/redisUtils"
	"time"
)

//用户记录有多少用户Id下单成功
var count = 0

type ISpike interface {
	QueryRedisV1(id int) (*int, error)
	QueryRedisV2(id int) (*int, error)
	AddRedis(id int, count int64) error
}

type Spike struct {
	base.BaseModel
	redis1.BaseRedisModel
}

func (s *Spike) QueryRedisV2(id int) (*int, error) {

	lock, err := redisUtils.TryLock(strconv.Itoa(id), 3*time.Second)
	if err != nil {
		return nil, err
	}
	if !lock {
		return nil, errors.New("操作过于频繁，请稍后在试！！")
	}
	defer func() {
		//fmt.Println("释放了锁")
		redisUtils.UnLock(strconv.Itoa(id))
	}()

	client, err := s.BaseRedisModel.GetClient()
	if err != nil {
		return nil, err
	}

	//判断订单缓存中是否已经存在这个用户Id了，以免重复下单
	result, err2 := client.HGetAll("save:userId").Result()
	if err2 != nil {
		return nil, err
	}
	//判断key是否存在,以及重复下单
	_, ok := result[strconv.Itoa(id)]
	if ok {
		return nil, errors.New("订单已经存在,请不要重复下单")
	}

	//减掉redis库存
	redisCount, err1 := client.DecrBy("id:"+strconv.Itoa(1), 1).Result()
	if err1 != nil {
		return nil, errors.New("扣减库存失败: " + err1.Error())
	}
	if redisCount >= 0 {
		//其实这里就可以记录下来抢购到的用户Id，然后做后续的业务处理，其实也就是统计有哪些用户已经下单成功了。
		//将下单的用户id保存起来。
		//其实这里的业务，如果你是百万级用户，也可以将用户Id写入到RabbitMq里面。
		_, err := client.HSet("save:userId", strconv.Itoa(id), id).Result()
		if err != nil {
			//这里如果说redis宕机了`或者其他异常的话，这里就应该用IncrBy增加一条库存数量，因为他是在decr减掉库存后才宕机的，所有要补偿1条库存。
			return nil, err
		}
		return nil, nil

	} else {
		fmt.Printf("库存不足,用户Id：%d 下单失败！\n", id)
		//补偿redis中的数据为0，如果不补偿redis中的数据会为负数
		_, err := client.Set("id:"+strconv.Itoa(1), 0, 0).Result()
		if err != nil {
			return nil, err
		}
	}

	return nil, nil
}

func NewSpike() ISpike {
	return &Spike{}
}

func (s *Spike) QueryRedisV1(id int) (*int, error) {
	client, err := s.BaseRedisModel.GetClient()
	if err != nil {
		return nil, err
	}

	//为了防止重复抢购下单，其实还的加一个限流//不存在则返回true，存在这个key了就返回false
	isFlag, err := client.SetNX("limit:userId:"+strconv.Itoa(id), id, time.Second*3).Result()
	if err != nil {
		return nil, err
	}
	if !isFlag {
		return nil, errors.New("操作过于频繁，请稍后在试！")
	}
	//判断订单缓存中是否已经存在这个用户Id了，以免重复下单
	result, err2 := client.HGetAll("save:userId").Result()
	if err2 != nil {
		return nil, err
	}
	//判断key是否存在,以及重复下单
	_, ok := result[strconv.Itoa(id)]
	if ok {
		return nil, errors.New("订单已经存在,请不要重复下单")
	}

	//减掉redis库存
	redisCount, err1 := client.DecrBy("id:"+strconv.Itoa(1), 1).Result()
	if err1 != nil {
		return nil, errors.New("扣减库存失败: " + err1.Error())
	}
	if redisCount >= 0 {
		//其实这里就可以记录下来抢购到的用户Id，然后做后续的业务处理，其实也就是统计有哪些用户已经下单成功了。
		//将下单的用户id保存起来。
		//其实这里的业务，如果你是百万级用户，也可以将用户Id写入到RabbitMq里面。
		_, err := client.HSet("save:userId", strconv.Itoa(id), id).Result()
		if err != nil {
			//这里如果说redis宕机了`或者其他异常的话，这里就应该用IncrBy增加一条库存数量，因为他是在decr减掉库存后才宕机的，所有要补偿1条库存。
			return nil, err
		}
		return nil, nil

	} else {
		fmt.Printf("库存不足,用户Id：%d 下单失败！\n", id)
		//补偿redis中的数据为0，如果不补偿redis中的数据会为负数
		_, err := client.Set("id:"+strconv.Itoa(1), 0, 0).Result()
		if err != nil {
			return nil, err
		}
	}
	return nil, nil
}

func (s *Spike) AddRedis(id int, count int64) error {
	//通过继承关系拿redis实例化对象
	client, err := s.BaseRedisModel.GetAloneClient()
	if err != nil {
		return err
	}

	//先清空库存
	_, err3 := client.Del("id:" + strconv.Itoa(id)).Result()
	if err3 != nil {
		return err3
	}

	//添加库存
	_, err1 := client.IncrBy("id:"+strconv.Itoa(id), count).Result()
	if err1 != nil {
		return errors.New("redis写入失败：" + err1.Error())
	}

	return nil
}
